Skip to content

fix(security): SSRF sub-resource + fail-closed, webhook 500, trusted rate-limit IP#2

Merged
vedantggwp merged 1 commit into
masterfrom
cc/security-hardening-followups
Jun 11, 2026
Merged

fix(security): SSRF sub-resource + fail-closed, webhook 500, trusted rate-limit IP#2
vedantggwp merged 1 commit into
masterfrom
cc/security-hardening-followups

Conversation

@vedantggwp

Copy link
Copy Markdown
Owner

Summary

Post-merge security follow-ups from the PR #1 review (3 independent sub-agents + orchestrator verification). All fixes verified locally.

scan-service SSRF — scanner.ts → new request-guard.ts

  • Validate every http(s) request (main nav, redirect hops, AND sub-resources) via DNS-resolving checkHostSafety — closes the hostname sub-resource SSRF hole (only literal IPs were blocked before).
  • Fail closed: guard errors now abort() instead of continue().
  • Pass through data:/blob:; per-scan host cache.
  • Extracted to its own module; 7 offline unit tests added.

webhook idempotency — app/app/api/webhook/route.ts

  • Return 500 on non-23505 insert failures so Stripe retries. A 200 silently dropped a paid report. The pre-insert lookup + 23505 guard make retries safe.

rate-limit key — app/lib/client-ip.ts + 6 routes

  • Derive client IP from x-real-ip / last XFF hop, not the spoofable left-most x-forwarded-for token.

Test plan

  • scan-service: tsc clean, 59/59 vitest (7 new request-guard tests)
  • app: tsc --noEmit clean
  • Integration: live scan against a redirect/sub-resource target

Deliberately deferred (need E2E I can't run blind)

  • DNS-rebind IP-pinning — resolve-then-connect TOCTOU; needs --host-resolver-rules/CDP peer-IP + a rebind harness.
  • Coupon max_uses atomicity — atomic RPC + caller rejection; touches the Stripe path.
  • report-status dropping email — UI-coupled.

…rate-limit IP

Post-merge follow-ups from the PR #1 review (3 sub-agents + verification).

scan-service SSRF (scanner.ts -> request-guard.ts):
- Validate EVERY http(s) request -- main nav, redirect hops, AND sub-resources
  -- via DNS-resolving checkHostSafety, not just literal-IP hosts. Closes the
  hostname sub-resource SSRF hole (e.g. an <img src> to a name resolving to a
  private/metadata IP).
- Fail CLOSED: guard errors now abort the request (was req.continue()).
- Pass through non-network schemes (data:/blob:). Per-scan host cache.
- Extracted to request-guard.ts; 7 offline unit tests.

webhook (route.ts):
- Return 500 on non-23505 insert failures so Stripe retries; a 200 silently
  dropped a paid report. Pre-insert lookup + 23505 guard make retries safe.

rate-limit (client-ip.ts + all 6 routes):
- Derive client IP from x-real-ip / last XFF hop, not the spoofable left-most
  x-forwarded-for token.

Verified: scan-service tsc clean + 59/59 tests; app tsc --noEmit clean.
DNS-rebind IP-pinning + coupon max_uses atomicity tracked as follow-ups.
@vedantggwp vedantggwp merged commit f606612 into master Jun 11, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant